package server;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.io.Serializable;

import client.GameConstants;
import client.Equip;
import client.IEquip;
import client.IItem;
import client.Item;
import client.MapleClient;
import client.MapleInventoryType;
import database.DatabaseConnection;
import database.DatabaseException;
import tools.MaplePacketCreator;

public class MapleStorage implements Serializable {

    private static final long serialVersionUID = 9179541993413738569L;
    private int id;
    private List<IItem> items;
    private int meso;
    private byte slots;
    private boolean changed = false;
    private Map<MapleInventoryType, List<IItem>> typeItems = new HashMap<MapleInventoryType, List<IItem>>();

    private MapleStorage(int id, byte slots, int meso) {
	this.id = id;
	this.slots = slots;
	this.items = new LinkedList<IItem>();
	this.meso = meso;
    }

    public static int create(int id) throws SQLException {
	Connection con = DatabaseConnection.getConnection();
	PreparedStatement ps = con.prepareStatement("INSERT INTO storages (accountid, slots, meso) VALUES (?, ?, ?)", DatabaseConnection.RETURN_GENERATED_KEYS);
	ps.setInt(1, id);
	ps.setInt(2, 4);
	ps.setInt(3, 0);
	ps.executeUpdate();

	int storageid;
	ResultSet rs = ps.getGeneratedKeys();
	if (rs.next()) {
	    storageid = rs.getInt(1);
	    ps.close();
	    rs.close();
	    return storageid;
	}
	ps.close();
	rs.close();
	throw new DatabaseException("Inserting char failed.");
    }

    public static MapleStorage loadStorage(int id) {
	MapleStorage ret = null;
	int storeId;
	try {
	    Connection con = DatabaseConnection.getConnection();
	    PreparedStatement ps = con.prepareStatement("SELECT * FROM storages WHERE accountid = ?");
	    ps.setInt(1, id);
	    ResultSet rs = ps.executeQuery();

	    if (rs.next()) {
		storeId = rs.getInt("storageid");
		ret = new MapleStorage(storeId, rs.getByte("slots"), rs.getInt("meso"));
		rs.close();
		ps.close();

		ps = con.prepareStatement("SELECT * FROM inventoryitems LEFT JOIN inventoryequipment USING (inventoryitemid) WHERE storageid = ?");
		ps.setInt(1, storeId);
		rs = ps.executeQuery();
		while (rs.next()) {
		    MapleInventoryType type = MapleInventoryType.getByType((byte) rs.getInt("inventorytype"));
		    if (type.equals(MapleInventoryType.EQUIP) || type.equals(MapleInventoryType.EQUIPPED)) {
			int itemid = rs.getInt("itemid");
			Equip equip = new Equip(itemid, rs.getByte("position"), rs.getInt("ringid"), rs.getByte("flag"));
			equip.setOwner(rs.getString("owner"));
			equip.setQuantity(rs.getShort("quantity"));
			equip.setAcc(rs.getShort("acc"));
			equip.setAvoid(rs.getShort("avoid"));
			equip.setDex(rs.getShort("dex"));
			equip.setHands(rs.getShort("hands"));
			equip.setHp(rs.getShort("hp"));
			equip.setInt(rs.getShort("int"));
			equip.setJump(rs.getShort("jump"));
			equip.setLuk(rs.getShort("luk"));
			equip.setMatk(rs.getShort("matk"));
			equip.setMdef(rs.getShort("mdef"));
			equip.setMp(rs.getShort("mp"));
			equip.setSpeed(rs.getShort("speed"));
			equip.setStr(rs.getShort("str"));
			equip.setWatk(rs.getShort("watk"));
			equip.setWdef(rs.getShort("wdef"));
			equip.setItemLevel(rs.getByte("itemLevel"));
			equip.setItemEXP(rs.getShort("itemEXP"));
			equip.setUpgradeSlots(rs.getByte("upgradeslots"));
			equip.setLevel(rs.getByte("level"));
			equip.setViciousHammer(rs.getByte("ViciousHammer"));
			equip.setPotential(rs.getInt("potential"));
			equip.setPotentialStats1(rs.getShort("potentialstats1"));
            equip.setPotentialStats2(rs.getShort("potentialstats2"));
            equip.setPotentialStats3(rs.getShort("potentialstats3"));
			equip.setExpiration(rs.getLong("expiredate"));
			equip.setFlag(rs.getByte("flag"));
			ret.items.add(equip);
		    } else {
			Item item = new Item(rs.getInt("itemid"), (byte) rs.getInt("position"), (short) rs.getInt("quantity"), rs.getByte("flag"));
			item.setOwner(rs.getString("owner"));
			item.setExpiration(rs.getLong("expiredate"));
			item.setFlag(rs.getByte("flag"));
			ret.items.add(item);
		    }
		}
		rs.close();
		ps.close();
	    } else {
		storeId = create(id);
		ret = new MapleStorage(storeId, (byte) 4, 0);
	    }
	} catch (SQLException ex) {
	    System.err.println("Error loading storage" + ex);
	}
	return ret;
    }

    public void saveToDB() {
	if (!changed) {
	    return;
	}
	try {
	    Connection con = DatabaseConnection.getConnection();

	    PreparedStatement ps = con.prepareStatement("UPDATE storages SET slots = ?, meso = ? WHERE storageid = ?");
	    ps.setInt(1, slots);
	    ps.setInt(2, meso);
	    ps.setInt(3, id);
	    ps.executeUpdate();
	    ps.close();

	    ps = con.prepareStatement("DELETE FROM inventoryitems WHERE storageid = ?");
	    ps.setInt(1, id);
	    ps.executeUpdate();
	    ps.close();

	    ps = con.prepareStatement("INSERT INTO inventoryitems (storageid, itemid, inventorytype, position, quantity, owner, GM_Log, expiredate, flag) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", DatabaseConnection.RETURN_GENERATED_KEYS);
	    PreparedStatement pse = con.prepareStatement("INSERT INTO inventoryequipment VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");

	    for (IItem item : items) {
		ps.setInt(1, id);
		ps.setInt(2, item.getItemId());
		ps.setInt(3, item.getType()); // type.getType()
		ps.setInt(4, item.getPosition());
		ps.setInt(5, item.getQuantity());
		ps.setString(6, item.getOwner());
		ps.setString(7, item.getGMLog());
		ps.setLong(8, item.getExpiration());
		ps.setByte(9, item.getFlag());
		ps.executeUpdate();

		ResultSet rs = ps.getGeneratedKeys();
		int itemid;
		if (rs.next()) {
		    itemid = rs.getInt(1);
		} else {
		    throw new DatabaseException("Inserting char failed.");
		}
		if (item.getType() == 1) {
		    pse.setInt(1, itemid);
		    IEquip equip = (IEquip) item;
		    pse.setInt(2, equip.getUpgradeSlots());
		    pse.setInt(3, equip.getLevel());
		    pse.setInt(4, equip.getStr());
		    pse.setInt(5, equip.getDex());
		    pse.setInt(6, equip.getInt());
		    pse.setInt(7, equip.getLuk());
		    pse.setInt(8, equip.getHp());
		    pse.setInt(9, equip.getMp());
		    pse.setInt(10, equip.getWatk());
		    pse.setInt(11, equip.getMatk());
		    pse.setInt(12, equip.getWdef());
		    pse.setInt(13, equip.getMdef());
		    pse.setInt(14, equip.getAcc());
		    pse.setInt(15, equip.getAvoid());
		    pse.setInt(16, equip.getHands());
		    pse.setInt(17, equip.getSpeed());
		    pse.setInt(18, equip.getJump());
		    pse.setInt(19, equip.getRingId());
		    pse.setInt(20, equip.getViciousHammer());
			pse.setInt(21, equip.getPotential());
            pse.setInt(22, equip.getPotentialStats1());
            pse.setInt(23, equip.getPotentialStats2());
            pse.setInt(24, equip.getPotentialStats3());
		    pse.setInt(25, equip.getItemLevel());
		    pse.setInt(26, equip.getItemEXP());
		    pse.executeUpdate();
		}
	    }
	    ps.close();
	    pse.close();
	} catch (SQLException ex) {
	    System.err.println("Error saving storage" + ex);
	}
    }

    public IItem takeOut(byte slot) {
	changed = true;
	IItem ret = items.remove(slot);
	MapleInventoryType type = GameConstants.getInventoryType(ret.getItemId());
	typeItems.put(type, new ArrayList<IItem>(filterItems(type)));
	return ret;
    }

    public void store(IItem item) {
	changed = true;
	items.add(item);
	MapleInventoryType type = GameConstants.getInventoryType(item.getItemId());
	typeItems.put(type, new ArrayList<IItem>(filterItems(type)));
    }

    public List<IItem> getItems() {
	return Collections.unmodifiableList(items);
    }

    private List<IItem> filterItems(MapleInventoryType type) {
	List<IItem> ret = new LinkedList<IItem>();

	for (IItem item : items) {
	    if (GameConstants.getInventoryType(item.getItemId()) == type) {
		ret.add(item);
	    }
	}
	return ret;
    }

    public byte getSlot(MapleInventoryType type, byte slot) {
	// MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
	byte ret = 0;
	for (IItem item : items) {
	    if (item == typeItems.get(type).get(slot)) {
		return ret;
	    }
	    ret++;
	}
	return -1;
    }

    public void sendStorage(MapleClient c, int npcId) {
	// sort by inventorytype to avoid confusion
	Collections.sort(items, new Comparator<IItem>() {

	    public int compare(IItem o1, IItem o2) {
		if (GameConstants.getInventoryType(o1.getItemId()).getType() < GameConstants.getInventoryType(o2.getItemId()).getType()) {
		    return -1;
		} else if (GameConstants.getInventoryType(o1.getItemId()) == GameConstants.getInventoryType(o2.getItemId())) {
		    return 0;
		} else {
		    return 1;
		}
	    }
	});
	for (MapleInventoryType type : MapleInventoryType.values()) {
	    typeItems.put(type, new ArrayList<IItem>(items));
	}
	c.getSession().write(MaplePacketCreator.getStorage(npcId, slots, items, meso));
    }

    public void sendStored(MapleClient c, MapleInventoryType type) {
	c.getSession().write(MaplePacketCreator.storeStorage(slots, type, typeItems.get(type)));
    }

    public void sendTakenOut(MapleClient c, MapleInventoryType type) {
	c.getSession().write(MaplePacketCreator.takeOutStorage(slots, type, typeItems.get(type)));
    }

    public int getMeso() {
	return meso;
    }

    public void setMeso(int meso) {
	if (meso < 0) {
	    return;
	}
        changed = true;
	this.meso = meso;
    }

    public void sendMeso(MapleClient c) {
	c.getSession().write(MaplePacketCreator.mesoStorage(slots, meso));
    }

    public boolean isFull() {
	return items.size() >= slots;
    }

    public int getSlots() {
	return slots;
    }

    public void increaseSlots(byte gain) {
        changed = true;
	this.slots += gain;
    }

    public void setSlots(byte set) {
        changed = true;
	this.slots = set;
    }

    public void close() {
	typeItems.clear();
    }
}
